# -*- coding: utf-8 -*-

# データimporter
class DataImporter
  def initialize
    # 各種ディレクトリ値定義
    @top_dir = File.join(Rails.root, "db", "mg_data")
    @user_base_dir    = File.join(@top_dir, "users")
    @blog_base_dir    = File.join(@top_dir, "blogs")
    @message_base_dir = File.join(@top_dir, "messages")
    @comm_base_dir    = File.join(@top_dir, "communities")

    @user_csv_file    = File.join(@user_base_dir, "user.csv")
    @friend_csv_file  = File.join(@user_base_dir, "friend.csv")
    @blog_csv_file    = File.join(@blog_base_dir, "blog.csv")
    @message_csv_file = File.join(@message_base_dir, "message.csv")
    @comm_csv_file    = File.join(@comm_base_dir, "community.csv")
    @board_csv_file   = File.join(@comm_base_dir, "thread.csv")
    @reply_csv_file   = File.join(@comm_base_dir, "reply.csv")

    @blog_attachment_base_dir = File.join(Rails.root, 'db', 'uploads', 'blog_attachment', 'attachment_file')
    @comm_attachment_base_dir = File.join(Rails.root, 'db', 'uploads', 'board_message_attachment', 'attachment_file')
    @board_attachment_base_dir = File.join(Rails.root, 'db', 'uploads', 'board_attachment', 'attachment_file')
    @message_attachment_base_dir = File.join(Rails.root, 'db', 'uploads', 'message_attachment', 'attachment_file')

    # マイグレーションログ
    @mg_log = Logger.new(File.join(Rails.root, "log", "data_migration.log"))
    @user_mg_log = Logger.new(File.join(Rails.root, "log", "user_migrate.log"))
    @message_mg_log = Logger.new(File.join(Rails.root, "log", "message_migrate.log"))
    @blog_mg_log = Logger.new(File.join(Rails.root, "log", "blog_migrate.log"))
    @community_mg_log = Logger.new(File.join(Rails.root, "log", "community_migrate.log"))
    @board_mg_log = Logger.new(File.join(Rails.root, "log", "board_migrate.log"))
    @board_message_mg_log = Logger.new(File.join(Rails.root, "log", "board_message_migrate.log"))

    # Tokyoのtimezone
    @tz = ActiveSupport::TimeZone.new("Tokyo")
  end

  # ユーザーデータの移行処理
  def mg_user_data
    @user_store = {}
    ActiveRecord::Base.transaction do
      user_data_import
      friend_data_import
    end
  end

  # メッセージデータの移行処理
  def mg_message_data
    @user_store = load_user_master
    ActiveRecord::Base.transaction do
      message_data_import
    end
  end

  # ブログデータの移行処理
  def mg_blog_data
    @user_store = load_user_master
    ActiveRecord::Base.transaction do
      blog_data_import
    end
  end

  # コミュニティデータの移行処理
  def mg_community_data
    @user_store = load_user_master
    @community_store = {}
    @board_store = {}
    @board_message_store = {}
    @board_message_reply_store = {}

    ActiveRecord::Base.transaction do
      puts "begin community import"
      community_data_import
      puts "complete community import"

      puts "begin board import"
      board_data_import
      puts "complete board import"

      puts "begin board message import"
      board_message_data_import
      puts "complete board message import"
    end
  end

  # 日時の文字列を受け取り、parseしてlocalタイムのDateTimeオブジェクトに変換する
  def parse_to_local_time(str)
    @tz.parse(str).to_datetime
  end

  private

  # APIメソッド群

  # ユーザーCSVからマスタを読み込む
  def load_user_master
    user_store = {}
    first_rec_flag = false
    CSV.parse(File.read(@user_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      user_store[row[0]] = row[2]
    end
    return user_store
  end

  # インポーター本体群

  # ユーザー情報インポーター
  def user_data_import
    first_rec_flag = false
    CSV.parse(File.read(@user_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      new_password = row[6]

      user = User.new(login: row[2],
                      nick_name: row[1],
                      password: new_password,
                      password_confirmation: new_password)
      if user.save
        setting = Setting.new(message_format_type: "plain",
                              blog_format_type: "markdown")
        setting.user = user

        prof_params = {permissions: "none",
                       blog_title: row[3],
                       comment: row[4],
                       birth_day: parse_to_local_time(row[5])}

        img = File.join(@user_base_dir, "#{row[0]}.jpg")
        if File.exist?(img)
          avatar_img = File.open(img, "rb")
          prof_params[:avatar] = avatar_img
        else
          avatar_img = nil
        end
        profile = Profile.new(prof_params)
        profile.user = user
        avatar_img.close if avatar_img

        if setting.save && profile.save
          @user_store[row[0]] = row[2]
          puts "#{row[0]}: #{row[2]}, created."
          @user_mg_log.info "#{row[0]}, #{user.id}"
        else
          raise "setting or profile save failed."
        end
      else
        p user.errors
        raise "User not saved"
      end
    end
  end

  # フレンド情報インポーター
  def friend_data_import
    first_rec_flag = false
    CSV.parse(File.read(@friend_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      src_user = User.where(login: @user_store[row[0]]).first
      dst_user = User.where(login: @user_store[row[1]]).first
      if src_user && dst_user
        if src_user.make_friend(dst_user)
          puts "Friendship created from #{src_user.login} to #{dst_user.login}"
        else
          raise "Can't create friendship #{row[0]} to #{row[1]}"
        end
      else
        raise "Friend List invalid."
      end
    end
  end

  # ブログ情報インポーター
  def blog_data_import
    FileUtils.mkdir_p(@blog_attachment_base_dir) unless File.exist?(@blog_attachment_base_dir)
    first_rec_flag = false
    CSV.parse(File.read(@blog_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      user = User.where(login: @user_store[row[0]]).first
      raise "User not found" unless user

      c_time = parse_to_local_time(row[2])
      article = Article.new(:user_id      => user.id,
                            :deleted_flag => false,
                            :permissions  => 'none',
                            :comment_permissions => 'none',
                            :format_type  => 'plain',
                            :title        => row[3],
                            :body         => row[4],
                            :created_at   => c_time)
      unless article.save
        raise "Article save failed."
      end

      # 添付ファイルの移行
      attachment_path = File.join(@blog_base_dir, row[1])
      next unless File.exist?(attachment_path)

      Dir.glob(File.join(attachment_path, '*')) do |item|
        base_name = File.basename(item)
        extension = base_name.split(".").last
        attachment_file = File.open(item, "rb")
        case extension
          when "jpg", "jpeg"
            ctype = "image/jpeg"
          when "png"
            ctype = "image/png"
          when "gif"
            ctype = "image/gif"
          else
            ctype = "application/octet-stream"
            attachment_file = nil
        end
        file_size = attachment_file ? attachment_file.size : 0

        ba = BlogAttachment.new(:filename => base_name,
                                :size => file_size,
                                :content_type => ctype,
                                :article_id => article.id,
                                :attachment_file => attachment_file)
        if ba.save
          target_dir = File.join(@blog_attachment_base_dir, ba.id.to_s)
          FileUtils.mkdir(target_dir) unless File.exist?(target_dir)
          FileUtils.cp(item, File.join(target_dir, base_name))
        else
          err_mes = "[Blog][unsupported file] user_id: #{row[0]}, blog_id: #{row[1]}, user: #{user.login}, file: #{item}"
          @mg_log.error err_mes
          raise "BlogAttachment save failed."
        end
        attachment_file.close if attachment_file
      end
      puts "#{user.login}'s #{row[1]}: 「#{row[3]}」 migrated."
      @blog_mg_log.info "#{row[1]}, #{article.id}"
    end
  end

  # コミュニティ情報インポーター
  def community_data_import
    FileUtils.mkdir_p(@comm_attachment_base_dir) unless File.exist?(@comm_attachment_base_dir)

    # コミュニティ作成
    first_rec_flag = false
    CSV.parse(File.read(@comm_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      community_params = {name:                    row[1],
                          description:             "no description",
                          join_style:              "acceptable",
                          permissions:             "none",
                          create_board_permission: "administrators",
                          created_at: parse_to_local_time(row[2])}

      img = File.join(@comm_base_dir, "#{row[0]}.jpg")
      if File.exist?(img)
        avatar_img = File.open(img, "rb")
        community_params[:avatar] = avatar_img
      else
        avatar_img = nil
      end
      community = Community.create(community_params)
      @community_store[row[0]] = community.id

      # 管理者の設定
      master_user = User.where(login: @user_store[row[3]]).first
      unless master_user
        master_user = User.where(login: "quentin").first
        @mg_log.error "[Community] Master user not found. quentin is master of [#{row[1]}]."
      end
      master_role = Role.community_master
      community_master = CommunityUser.new(:user_id => master_user.id,
                                         :community_id => community.id,
                                         :role_id => master_role.id)
      unless community_master.save
        raise "Failed to create community master at [#{row[1]}]"
      end

      # コミュニティメンバーの登録
      row[4].split(/\s*,\s*/).each do |user_code|
        community_user = User.where(login: @user_store[user_code]).first
        next unless community_user

        user_role = Role.community_user
        community_user = CommunityUser.new(:user_id => community_user.id,
                                           :community_id => community.id,
                                           :role_id => user_role.id)
        unless community_user.save
          raise "Failed to create community user at [#{row[1]}]"
        end
      end
      @community_mg_log.info "#{row[0]}, #{community.id}"
    end
  end

  # 掲示板情報インポーター
  def board_data_import
    first_rec_flag = false
    CSV.parse(File.read(@board_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      # 存在しないコミュニティへのトピックは無視
      next unless @community_store.key?(row[0])

      community = Community.find(@community_store[row[0]])
      user = User.where(login: @user_store[row[4]]).first
      unless user
        user = User.where(login: "quentin").first
      end

      board = Board.new(title: row[5],
                        description: row[6],
                        created_at: parse_to_local_time(row[3]))
      board.community = community
      board.owner = user
      unless board.save
        @mg_log.error "[Board] Save failed: #{board.errors.inspect}"
        raise "Invalid Board params."
      end
      @board_store[row[1]] = board.id
      @board_mg_log.info "#{row[1]}, #{board.id}"

      # 添付ファイルの移行
      attachment_path = File.join(@comm_base_dir, "threads", row[1])
      next unless File.exist?(attachment_path)

      Dir.glob(File.join(attachment_path, '*')) do |item|
        base_name = File.basename(item)
        extension = base_name.split(".").last
        attachment_file = File.open(item, "rb")
        case extension
          when "jpg", "jpeg"
            ctype = "image/jpeg"
          when "png"
            ctype = "image/png"
          when "gif"
            ctype = "image/gif"
          else
            ctype = "application/octet-stream"
            attachment_file = nil
        end
        file_size = attachment_file ? attachment_file.size : 0
        ba = BoardAttachment.new(:filename => base_name,
                                :size => file_size,
                                :content_type => ctype,
                                :board_id => board.id,
                                :attachment_file => attachment_file)
        if ba.save
          target_dir = File.join(@board_attachment_base_dir, ba.id.to_s)
          FileUtils.mkdir(target_dir) unless File.exist?(target_dir)
          FileUtils.cp(item, File.join(target_dir, base_name))
        else
          @mg_log.error "[Board][unsupported file] board_id: #{row[1]}, community_id: #{row[0]}, user: #{user.login}, file: #{item}"
          raise "BlogAttachment save failed."
        end
        attachment_file.close if attachment_file
      end
    end
  end

  # 掲示板メッセージインポーター
  def board_message_data_import
    first_rec_flag = false
    CSV.parse(File.read(@reply_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end
      # 削除済みメッセージはインポートしない
      next if row[7].to_i == 1

      user = User.where(login: @user_store[row[4]]).first

      # 存在しないユーザーのメッセージはインポートしない
      next unless user

      # 存在しない掲示板へのメッセージはインポートしない
      next unless @board_store.key?(row[1])

      ctime = parse_to_local_time(row[3])
      board = Board.find(@board_store[row[1]])
      mes_params = {subject: row[5],
                    body: row[6],
                    created_at: ctime}

      board_message = BoardMessage.new(mes_params)
      board_message.from_user = user
      board_message.format_type = "plain"
      board_message.board = board

      unless board_message.save
        raise "Invalid Message params"
      end
      @board_message_store[row[0]] = board_message.id
      @board_message_reply_store[row[0]] = row[2]

      # 添付ファイルの移行
      attachment_path = File.join(@comm_base_dir, "replies", row[0])
      next unless File.exist?(attachment_path)

      Dir.glob(File.join(attachment_path, '*')) do |item|
        base_name = File.basename(item)
        extension = base_name.split(".").last
        attachment_file = File.open(item, "rb")
        case extension
          when "jpg", "jpeg"
            ctype = "image/jpeg"
          when "png"
            ctype = "image/png"
          when "gif"
            ctype = "image/gif"
          else
            ctype = "application/octet-stream"
            attachment_file = nil
        end
        file_size = attachment_file ? attachment_file.size : 0
        ba = BoardMessageAttachment.new(:filename => base_name,
                                :size => file_size,
                                :content_type => ctype,
                                :board_message_id => board_message.id,
                                :attachment_file => attachment_file)
        if ba.save
          target_dir = File.join(@comm_attachment_base_dir, ba.id.to_s)
          FileUtils.mkdir(target_dir) unless File.exist?(target_dir)
          FileUtils.cp(item, File.join(target_dir, base_name))
        else
          @mg_log.error "[BoardMessage][unsupported file] board_messsage_id: #{row[0]}, board_id: #{row[1]}, user: #{user.login}, file: #{item}"
          raise "BlogMessageAttachment save failed."
        end
        attachment_file.close if attachment_file
      end
      @board_message_mg_log.info "#{row[0]}, #{board_message.id}"
    end

    # 掲示板メッセージの返信ID関連付け
    @board_message_reply_store.each do |bm_id, reply_id|
      bm = BoardMessage.find(@board_message_store[bm_id])
      bm.reply_to_id = @board_message_store[reply_id]
      bm.save
    end

  end

  # メッセージデータインポーター
  def message_data_import
    FileUtils.mkdir_p(@message_attachment_base_dir) unless File.exist?(@message_attachment_base_dir)

    first_rec_flag = false
    CSV.parse(File.read(@message_csv_file)).each do |row|
      unless first_rec_flag
        first_rec_flag = true
        next
      end

      from_user = User.where(login: @user_store[row[1]]).first
      raise "From user not found [#{row[1]}]" unless from_user

      to_user = User.where(login: @user_store[row[2]]).first
      raise "To user not found [#{row[2]}]" unless to_user

      delete_flag = row[6].to_i == 0 ? false : true
      read_flag = true

      message = Message.create(subject: row[4],
                               body: row[5],
                               from_user_id: from_user.id,
                               created_at: parse_to_local_time(row[3]))
      mr = MessageRecipient.create(user_id: to_user.id,
                                   message_id: message.id,
                                   recipient_type: "to",
                                   delete_flag: delete_flag,
                                   read_flag: read_flag,
                                   created_at: parse_to_local_time(row[3]))

      # 添付ファイルの移行
      attachment_path = File.join(@message_base_dir, row[0])
      next unless File.exist?(attachment_path)

      Dir.glob(File.join(attachment_path, '*')) do |item|
        base_name = File.basename(item)
        extension = base_name.split(".").last
        attachment_file = File.open(item, "rb")
        case extension
          when "jpg", "jpeg"
            ctype = "image/jpeg"
          when "png"
            ctype = "image/png"
          when "gif"
            ctype = "image/gif"
          else
            ctype = "application/octet-stream"
            attachment_file = nil
        end
        file_size = attachment_file ? attachment_file.size : 0
        ba = MessageAttachment.new(:filename => base_name,
                                   :size => file_size,
                                   :content_type => ctype,
                                   :message_id => message.id,
                                   :attachment_file => attachment_file)
        if ba.save
          target_dir = File.join(@message_attachment_base_dir, ba.id.to_s)
          FileUtils.mkdir(target_dir) unless File.exist?(target_dir)
          FileUtils.cp(item, File.join(target_dir, base_name))
        else
          @mg_log.error "[Message][unsupported file] messsage_id: #{row[0]}, user: #{user.login}, file: #{item}"
          raise "MessageAttachment save failed."
        end
        attachment_file.close if attachment_file
      end
      puts "#{row[0]}: imported."
      @message_mg_log.info "#{row[0]}, #{message.id}"
    end
  end
end
